﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.ComponentModel;

namespace VKontakteClient
{
    /// <summary>
    /// Описание делегата-коллбэка.
    /// </summary>
    public delegate void SimpleCallback();

    /// <summary>
    /// Класс, реализующий планировку работы потоков
    /// </summary>
    public class BackgroundThreadingScheduler:INotifyPropertyChanged
    {
        /// <summary>
        /// Класс-элемент списка потоков
        /// </summary>
        private class QueueItem
        {
            /// <summary>
            /// Рабочий поток
            /// </summary>
            public Thread Worker { get; private set; }

            /// <summary>
            /// Функция, возвращающая осмысленное описание потока
            /// </summary>
            public Func<string> Name { get; private set; }

            /// <summary>
            /// Функция, которая должна быть вызвана после завершения работы потока
            /// </summary>
            public SimpleCallback Callback { get; private set; }

            /// <summary>
            /// Инициализатор элемента очереди
            /// </summary>
            /// <param name="worker">Рабочий поток</param>
            /// <param name="name">Функция, получающая осмысленное имя потока</param>
            /// <param name="callback">Функция, которую надо вызвать по завершению работы рабочего потока</param>
            private void Init(Thread worker, Func<string> name, SimpleCallback callback)
            {
                Worker = worker;
                Name = name;
                Callback = callback;
            }

            /// <summary>
            /// Конструктор элемента очереди
            /// </summary>
            /// <param name="worker">Рабочий поток</param>
            /// <param name="name">Функция, получающая осмысленное имя потока</param>
            /// <param name="callback">Функция, которую надо вызвать по завершению работы рабочего потока</param>
            public QueueItem(Thread worker, Func<string> name, SimpleCallback callback)
            {
                Init(worker, name, callback);
            }
        }

        /// <summary>
        /// Список, хранящий рабочие задачи.
        /// </summary>
        private List<QueueItem> _workers;

        /// <summary>
        /// Поток работы самого планировщика задач.
        /// </summary>
        private Thread _scheduler;

        /// <summary>
        /// Количество задач
        /// </summary>
        public int Count
        {
            get
            {
                return _workers.Count;
            }
        }

        /// <summary>
        /// Сообщение случайной задачи
        /// </summary>
        public string RandomMessage
        {
            get
            {
                if (Count > 0) return _workers.OrderBy(v => new Random((int)DateTime.Now.Ticks).Next()).First().Name();
                return null;
            }
        }

        /// <summary>
        /// Перегруженный ToString(), получает статусное сообщение
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Задач: " + Count + ". " + RandomMessage;
        }

        /// <summary>
        /// Статусное сообщение планировщика задач
        /// </summary>
        public string Status
        {
            get
            {
                return ToString();
            }
        }

        /// <summary>
        /// Конструктор планировщика задач
        /// </summary>
        public BackgroundThreadingScheduler()
        {
#if DEBUG
            DebugLog.Dump("BackgroundThreadingScheduler()\r\n");
#endif
            _workers = new List<QueueItem>();
            _scheduler = new Thread(() => 
            {
                while (!Main.IsClosed || _workers.Count>0)
                {
                    if (Main.IsClosed)
                        _workers.RemoveAll(v => v!=null && v.Worker.ThreadState == ThreadState.Unstarted);
                    int WorkingCount = _workers.ToArray().Where(v => v != null && v.Worker.ThreadState == ThreadState.Running).Count() * 2;
                    var tempCount = _workers.ToArray().Where(v => v != null && v.Worker.ThreadState == ThreadState.WaitSleepJoin).Count();
                    var realThreadCount = tempCount + WorkingCount;
                    if (WorkingCount > 0)
                        WorkingCount += tempCount;
                    int Waiting = _workers.ToArray().Where(v => v!= null && v.Worker.ThreadState == ThreadState.Unstarted).Count();
                    if (Waiting > 0 && WorkingCount < 20 && realThreadCount<50)
                    {
                        var normal = _workers.ToArray().Where(v => v != null && v.Worker.ThreadState == ThreadState.Unstarted && v.Worker.Priority >= ThreadPriority.AboveNormal).Take(20-WorkingCount).ToArray();
                        foreach (var v in normal)
                        {
                            try
                            {
                                v.Worker.Start();
                                WorkingCount++;
                            }
                            catch { }
                        }
                        if (20>WorkingCount)
                            foreach (var v in _workers.ToArray().Where(v => v != null && v.Worker.ThreadState == ThreadState.Unstarted).Reverse().Take(20 - WorkingCount).ToArray())
                            {
                                try
                                {
                                    v.Worker.Start();
                                }
                                catch { }
                            }
                    }
                    var Stopped = _workers.ToArray().Where(v => v != null && !(v.Worker.ThreadState == ThreadState.Running || v.Worker.ThreadState == ThreadState.WaitSleepJoin || v.Worker.ThreadState == ThreadState.Unstarted));
                    foreach (var v in Stopped)
                    {
                        _workers.Remove(v);
                        if (v.Callback != null)
                            v.Callback();
                    }
                    Thread.Sleep(100);
                    if (/*Stopped.Count() > 0 && */PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Status"));
                }
            });
            _scheduler.Start();
        }

        /// <summary>
        /// Метод добавления задачи
        /// </summary>
        /// <param name="method">Функцию, которую выполнять</param>
        /// <param name="name">Функция получения имени</param>
        /// <param name="callback">Функция, которую надо вызвать после завершения работы основной функции</param>
        public void Enqueue(ThreadStart method, ThreadPriority tp, Func<string> name, SimpleCallback callback)
        {
            Enqueue(new Thread(method), tp, name,callback);
        }

        /// <summary>
        /// Метод добавления задачи
        /// </summary>
        /// <param name="thread">Рабочий поток, реализующий основную функцию задачи</param>
        /// <param name="name">Функция получения имени</param>
        /// <param name="callback">Функция, которую надо вызвать после завершения работы основной функции</param>
        public void Enqueue(Thread thread, ThreadPriority tp, Func<string> name, SimpleCallback callback)
        {
            if (thread != null && name != null && _workers != null)
            {
                thread.Priority = tp;
                _workers.Add(new QueueItem(thread, name, callback));
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Status"));
            }
        }

        /// <summary>
        /// Метод добавления задачи
        /// </summary>
        /// <param name="method">Функцию, которую выполнять</param>
        /// <param name="name">Функция получения имени</param>
        public void Enqueue(ThreadStart method, ThreadPriority tp, Func<string> name)
        {
            Enqueue(new Thread(method), tp,name, null);
        }

        /// <summary>
        /// Метод добавления задачи
        /// </summary>
        /// <param name="thread">Рабочий поток, реализующий основную функцию задачи</param>
        /// <param name="name">Функция получения имени</param>
        public void Enqueue(Thread thread, ThreadPriority tp, Func<string> name)
        {
            Enqueue(thread, tp,name, null);
        }

        /// <summary>
        /// Событие изменения свойств
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
    }
}
